package io.jpress.core.flowable;

import com.jfinal.kit.PathKit;
import com.jfinal.plugin.activerecord.DbKit;
import io.jboot.Jboot;
import io.jboot.utils.ClassScanner;
import io.jboot.utils.ClassUtil;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.common.engine.impl.de.odysseus.el.ExpressionFactoryImpl;
import org.flowable.common.engine.impl.de.odysseus.el.util.SimpleContext;
import org.flowable.common.engine.impl.javax.el.ExpressionFactory;
import org.flowable.common.engine.impl.javax.el.ValueExpression;
import org.flowable.engine.*;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.runtime.ChangeActivityStateBuilder;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;

import java.io.File;
import java.io.IOException;
import java.util.*;

public class FlowableUtil {

    private static ProcessEngine processEngine;

    //Map<类型,类型对应的 Class 列表>
    private static Map<String, LinkedList<DelegateInfo>> serviceTaskDelegates = new HashMap<>();

    public static ProcessEngine getProcessEngine() {
        if (processEngine == null) {
            synchronized (FlowableUtil.class) {
                if (processEngine == null) {
                    processEngine = buildProcessEngine();
                }
            }
        }
        return processEngine;
    }

    private static ProcessEngine buildProcessEngine() {

        List<Class> delegateClasses = ClassScanner.scanClassByAnnotation(AutoLoadServiceTaskDelegate.class, true);
        for (Class<?> delegateClass : delegateClasses) {
            DelegateInfo delegateInfo = new DelegateInfo(delegateClass, delegateClass.getAnnotation(AutoLoadServiceTaskDelegate.class));
            LinkedList<DelegateInfo> classLinkedList = serviceTaskDelegates.computeIfAbsent(delegateInfo.getType(), s -> new LinkedList<>());
            classLinkedList.add(delegateInfo);
            classLinkedList.sort(Comparator.comparingInt(DelegateInfo::getOrder));
        }


        List<FlowableEventListener> eventListeners = new ArrayList<>();
        if (Jboot.isDevMode()) {
            eventListeners.add(new ProcessEventPrinter());
        }

        Map<String, List<FlowableEventListener>> typedEventListeners = new HashMap<>();


        List<Class> classes = ClassScanner.scanClassByAnnotation(AutoLoadEventListener.class, true);
        for (Class<? extends FlowableEventListener> aClass : classes) {
            AutoLoadEventListener annotation = aClass.getAnnotation(AutoLoadEventListener.class);
            if (annotation.types().length == 0) {
                eventListeners.add(ClassUtil.newInstance(aClass));
            } else {
                for (FlowableEngineEventType type : annotation.types()) {
                    List<FlowableEventListener> listeners = typedEventListeners.computeIfAbsent(type.name(), k -> new ArrayList<>());
                    listeners.add(ClassUtil.newInstance(aClass));
                }
            }
        }


        ProcessEngineConfiguration cfg = new Configuration()
                .setActivityBehaviorFactory(new JPressActivityBehaviorFactory()) //节点流程监控
                .setEventListeners(eventListeners) //所有 Event 监听器
                .setTypedEventListeners(typedEventListeners) // Event 监听器
                .setDisableEventRegistry(true) // 不开启 event 模块，不建立 FLW_EVENT 表
                .setDisableIdmEngine(true) // 不开启 ID 管理模块，身份用自身系统了
                .setDataSource(DbKit.getConfig().getDataSource()); //数据源


        File checkDbSchemaLockFile = new File(PathKit.getRootClassPath(), "flowable_db_init.lock");
        if (!checkDbSchemaLockFile.exists()) {
            //自动建表，需要权限支持，一般可以主动创建
            cfg.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        } else {
            //不会自动建表，但是会对比版本，版本不对将会抛出异常
            cfg.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE);
        }

        ProcessEngine processEngine = cfg.buildProcessEngine();
        if (processEngine != null && !checkDbSchemaLockFile.exists()) {
            try {
                checkDbSchemaLockFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return processEngine;
    }


    public static RepositoryService getRepositoryService() {
        return getProcessEngine().getRepositoryService();
    }

    public static RuntimeService getRuntimeService() {
        return getProcessEngine().getRuntimeService();
    }

    public static FormService getFormService() {
        return getProcessEngine().getFormService();
    }

    public static TaskService getTaskService() {
        return getProcessEngine().getTaskService();
    }

    public static HistoryService getHistoryService() {
        return getProcessEngine().getHistoryService();
    }

    public static IdentityService getIdentityService() {
        return getProcessEngine().getIdentityService();
    }

    public static ManagementService getManagementService() {
        return getProcessEngine().getManagementService();
    }

    public static DynamicBpmnService getDynamicBpmnService() {
        return getProcessEngine().getDynamicBpmnService();
    }

    public static ProcessMigrationService getProcessMigrationService() {
        return getProcessEngine().getProcessMigrationService();
    }

    public static Configuration getProcessEngineConfiguration() {
        return (Configuration) getProcessEngine().getProcessEngineConfiguration();
    }

    public static LinkedList<DelegateInfo> getServiceTaskDelegates(String type) {
        getProcessEngine();
        return serviceTaskDelegates.get(type);
    }

    /**
     * 获取当前节点
     *
     * @param processDefId  流程定义的 ID
     * @param elementDefKey 节点元素的定义 key
     * @return
     */
    public static FlowElement getFlowElementByDefKey(String processDefId, String elementDefKey) {
        BpmnModel bpmnModel = getRepositoryService().getBpmnModel(processDefId);

        //每个 bpmn 流程图中，可能会有多个流程实例（包括父子流程等）
        List<Process> processes = bpmnModel.getProcesses();
        for (Process process : processes) {
            FlowElement flowElement = process.getFlowElementMap().get(elementDefKey);
            if (flowElement != null) {
                return flowElement;
            }
        }
        return null;
    }


    /**
     * 获取上个节点的 id
     *
     * @param processDefId  流程定义ID
     * @param currentDefKey 当前的节点定义Key
     * @return 上一个节点定义的 Id
     */
    public static String getPrevFlowElementId(String processDefId, String currentDefKey) {
        BpmnModel bpmnModel = getRepositoryService().getBpmnModel(processDefId);
        if (bpmnModel == null) {
            return null;
        }

        FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(currentDefKey);
        if (flowNode == null) {
            return null;
        }

        List<SequenceFlow> incomingFlows = flowNode.getIncomingFlows();
        if (incomingFlows == null || incomingFlows.isEmpty()) {
            return null;
        }

        return incomingFlows.get(0).getSourceRef();
    }


    /**
     * 获取所有 流出的线条
     *
     * @param processDefId
     * @param currentDefKey
     * @return
     */
    public static List<SequenceFlow> getOutgoingFlows(String processDefId, String currentDefKey) {
        BpmnModel bpmnModel = getRepositoryService().getBpmnModel(processDefId);
        if (bpmnModel == null) {
            return null;
        }

        FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(currentDefKey);
        if (flowNode == null) {
            return null;
        }

        return flowNode.getOutgoingFlows();
    }


    /**
     * 获取上一个流程节点
     * 若有多个流程同时流入当前节点，那么只获取第一个
     *
     * @param processDefId         流程定义的ID
     * @param currentElementDefKey 当前节点的流程定义 key
     * @return
     */
    public static FlowElement getPrevFlowElement(String processDefId, String currentElementDefKey) {
        String prevFlowElementId = getPrevFlowElementId(processDefId, currentElementDefKey);
        if (prevFlowElementId == null) {
            return null;
        }

        return getFlowElementByDefKey(processDefId, prevFlowElementId);
    }


    /**
     * 查找结束的节点
     *
     * @param processDefId
     * @return
     */
    public static String getEndFlowElementId(String processDefId) {
        Process mainProcess = getRepositoryService().getBpmnModel(processDefId).getMainProcess();
        Collection<FlowElement> list = mainProcess.getFlowElements();
        if (list == null || list.isEmpty()) {
            return null;
        }

        for (FlowElement flowElement : list) {
            if (flowElement instanceof EndEvent) {
                return flowElement.getId();
            }
        }

        return null;
    }

    /**
     * 终止流程
     *
     * @param processInstanceId 流程实例ID
     */
    public static void stopProcessInstance(String processInstanceId) {
        stopProcessInstance(processInstanceId, null);
    }

    public static boolean stopProcessInstance(String processInstanceId, Map<String, Object> processVariables) {

        ProcessInstance processInstance = getRuntimeService().createProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();

        if (processInstance == null){
            //当前实例在某情况可能已经被删除
            return false;
        }

        List<Execution> executions = getExecutions(processInstanceId);

        List<String> executionIds = new ArrayList<>();
        executions.forEach(execution -> executionIds.add(execution.getId()));

        String endFlowElementId = getEndFlowElementId(processInstance.getProcessDefinitionId());

        ChangeActivityStateBuilder changeActivityStateBuilder = getRuntimeService().createChangeActivityStateBuilder();
        changeActivityStateBuilder.processInstanceId(processInstanceId);

        if (processVariables != null) {
            changeActivityStateBuilder.processVariables(processVariables);
        }

        changeActivityStateBuilder.moveExecutionsToSingleActivityId(executionIds, endFlowElementId)
                .changeState();

        return true;
    }


    /**
     * Execution 执行实例，或者执行路线
     * 顺序执行/并发分支执行/子流程/嵌套流程
     *
     * @param processInstanceId
     * @return
     */
    public static List<Execution> getExecutions(String processInstanceId) {
        List<Execution> executions = getRuntimeService().createExecutionQuery()
                .parentId(processInstanceId).list();
        return executions;
    }


    /**
     * 移动流程到其他任意节点
     *
     * @param processInstanceId 当前的流程实例ID
     * @param currentActivityId 当前的 element key （element key 在BPMN 2.0术语中称作活动ID【activity id】）
     * @param toActivityId      跳转到的 element key （element key 在BPMN 2.0术语中称作活动ID【activity id】）
     */
    public static void moveActivityTo(String processInstanceId, String currentActivityId, String toActivityId) {
        moveActivityTo(processInstanceId, currentActivityId, toActivityId, null);
    }


    /**
     * 移动流程到其他任意节点
     *
     * @param processInstanceId 当前的流程实例ID
     * @param currentActivityId 当前的 element key （element key 在BPMN 2.0术语中称作活动ID【activity id】）
     * @param toActivityId      跳转到的 element key （element key 在BPMN 2.0术语中称作活动ID【activity id】）
     * @param processVariables  跳转时设置的 variables
     */
    public static void moveActivityTo(String processInstanceId, String currentActivityId, String toActivityId, Map<String, Object> processVariables) {
        ChangeActivityStateBuilder changeActivityStateBuilder = getRuntimeService().createChangeActivityStateBuilder();
        changeActivityStateBuilder.processInstanceId(processInstanceId);

        if (processVariables != null) {
            changeActivityStateBuilder.processVariables(processVariables);
        }

        changeActivityStateBuilder.moveActivityIdTo(currentActivityId, toActivityId)
                .changeState();
    }


    /**
     * 执行某一个表达式
     *
     * @param processInstanceId
     * @param exp
     * @return 返回表达式的执行结果
     */
    public static Object execExpression(String processInstanceId, String exp) {
        Expression expression = getProcessEngineConfiguration().getExpressionManager().createExpression(exp);
        ExecutionEntity executionEntity = (ExecutionEntity) getRuntimeService().createProcessInstanceQuery()
                .processInstanceId(processInstanceId).includeProcessVariables().singleResult();
        return getManagementService().executeCommand(commandContext -> expression.getValue(executionEntity));
    }


    /**
     * 执行某一个表达式
     * @param el
     * @param variableMap
     * @return 返回表达式的执行结果
     */
    public static Object execExpression(String el, Map variableMap) {
        ExpressionFactory factory = new ExpressionFactoryImpl();
        SimpleContext context = new SimpleContext();
        if (variableMap != null && !variableMap.isEmpty()) {
            for (Object k : variableMap.keySet()) {
                if (variableMap.get(k) != null) {
                    context.setVariable(k.toString(),
                            factory.createValueExpression(variableMap.get(k), variableMap.get(k).getClass()));
                } else {
                    context.setVariable(k.toString(),
                            factory.createValueExpression(null, Object.class));
                }
            }
        }
        ValueExpression valueExpression = factory.createValueExpression(context, el, Object.class);
        try {
            return valueExpression.getValue(context);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
